<template>
    <div>
        <el-dialog title="批量上传" width="60%" :visible.sync="fileDialogVisible">
            <el-upload action=""
                       :file-list="uploadFileList"
                       :show-file-list="false" :multiple="true"
                       :auto-upload="false"
                       :on-change="handleFileChange"
                       :on-success="handleFileUploadSuccess" style="display: inline-block">
                <el-button slot="trigger" type="primary" plain>选择文件</el-button>
                <el-button style="margin-left: 5px;" type="success" @click="handler" plain>开始上传</el-button>
                <el-button type="danger" @click="clearFileHandler" plain>清空</el-button>
            </el-upload>
            <table style="margin-top: 20px">
                <th style="display:inline-block;font-size: 10px;color: #909399;margin-left: 100px">
                    文件名
                </th>
                <th style="display:inline-block;;font-size: 12px;color: #909399;margin-left: 191px;">
                    文件大小
                </th>
                <th style="display:inline-block;;font-size: 12px;color: #909399;margin-left: 100px">
                    上传进度
                </th>
                <th style="display:inline-block;;font-size: 12px;color: #909399;margin-left: 100px">
                    功能
                </th>
                <th style="display:inline-block;;font-size: 12px;color: #909399;margin-left: 100px">
                    状态
                </th>
            </table>
            <div class="file-list-wrapper">
                <el-collapse>
                    <el-collapse-item v-for="(item, index) in uploadFileList" :key="index">
                        <template slot="title">
                            <div class="upload-file-item">
                                <div class="file-info-item file-name" :title="item.name">{{ item.name }}</div>
                                <div class="file-info-item file-size">{{ item.size | transformByte }}</div>
                                <div class="file-info-item file-progress">
                                    <span class="file-progress-label"></span>
                                    <el-progress :percentage="item.uploadProgress" class="file-progress-value"/>
                                </div>
                                <el-button style="margin-top: 10px;margin-bottom: 10px" type="primary" size="mini" @click="deleteFile(item)">移除</el-button>
                                <div class="file-info-item file-size"><span></span>
                                    <el-tag v-if="item.status === '等待上传'" size="medium" type="info">等待上传</el-tag>
                                    <el-tag v-else-if="item.status === '校验MD5'" size="medium" type="warning">校验MD5</el-tag>
                                    <el-tag v-else-if="item.status === '正在上传'" size="medium">正在上传</el-tag>
                                    <el-tag v-else-if="item.status === '上传成功'" size="medium" type="success">上传完成</el-tag>
                                    <el-tag v-else size="medium">正在上传</el-tag>
                                    <!--                                <el-tag v-else size="medium" type="danger">上传错误</el-tag>-->
                                </div>
                            </div>
                        </template>
                        <div class="file-chunk-list-wrapper">
                            <!-- 分片列表 -->
                            <el-table :data="item.chunkList" max-height="400" style="width: 100%">
                                <el-table-column prop="chunkNumber" label="分片序号" width="180">
                                </el-table-column>
                                <el-table-column prop="progress" label="上传进度">
                                    <template v-slot="{ row }">
                                        <el-progress v-if="!row.status || row.progressStatus === 'normal'"
                                                     :percentage="row.progress"/>
                                        <el-progress v-else :percentage="row.progress" :status="row.progressStatus"
                                                     :text-inside="true" :stroke-width="16"/>
                                    </template>
                                </el-table-column>
                                <el-table-column prop="status" label="状态" width="180">
                                </el-table-column>
                            </el-table>
                        </div>
                    </el-collapse-item>
                </el-collapse>
            </div>
        </el-dialog>
    </div>
</template>

<script>
    import {checkUpload, initUpload, mergeUpload, uploadFileInfo} from "@/api/upload";

    import {fileSuffixTypeUtil} from "@/utils/FileUtil"

    import axios from 'axios'

    import SparkMD5 from 'spark-md5'

    const FILE_UPLOAD_ID_KEY = 'file_upload_id'

    const chunkSize = 10 * 1024 * 1024 //10mb

    let currentFileIndex = 0

    const FileStatus = {
        wait: '等待上传',
        getMd5: '校验MD5',
        chip: '正在创建序列',
        uploading: '正在上传',
        success: '上传成功',
        error: '上传错误'
    }

    export default {
        components: {},
        props: {
            fileTableVisible:Boolean,
        },
        data() {
            return {
                // 上传并发数
                simultaneousUploads: 3,
                uploadIdInfo: null,
                uploadFileList: [],
                pageNum: 1,
                pageSize: 10,
                total: 0,
            };
        },
        computed: {
            fileDialogVisible:{
                get(){
                   return this.fileTableVisible
                },
                set(value){
                    this.$emit("changeFileDialogVisible",value)
                }
            }
        },

        watch:{},

        methods: {
            /**
             * 开始上传文件
             */
            handler() {
                const self = this
                //判断文件列表是否为空
                if (this.uploadFileList.length === 0) {
                    this.$message.error('请先选择文件')
                    return
                }
                if (currentFileIndex >= this.uploadFileList.length) {
                    if (this.uploadFileList.filter(item => item.status !== "上传成功")) {
                        this.$message.success("全部上传成功")
                        this.$emit("uploadFileList")
                    }
                    this.$nextTick(()=>{
                        this.$emit("uploadFileList")
                    })
                    //this.$message.success("文件上传成功")
                    return;
                }
                //当前操作文件
                const currentFile = this.uploadFileList[currentFileIndex]
                console.log("当前操作文件：", currentFile)
                //debugger
                //更新上传标签
                currentFile.status = FileStatus.getMd5
                currentFile.chunkUploadedList = []

                //截取封面图片
                //this.ScreenshotVideo(currentFile.raw);

                // 1. 计算文件MD5
                this.getFileMd5(currentFile.raw, async (md5, totalChunks) => {
                    console.log("md5值", md5)
                    // 2. 检查是否已上传
                    const checkResult = await self.checkFileUploadedByMd5(md5)
                    console.log("检查是否已上传-->",checkResult)
                    // debugger
                    if (checkResult.code === 1) {
                        //self.$message.success(`上传成功，文件地址：${checkResult.data.url}`)
                        console.log('上传成功文件访问地址：' + checkResult.data.url)
                        currentFile.status = FileStatus.success
                        currentFile.uploadProgress = 100
                        //如果此文件上传过，就跳到下一个文件
                        currentFileIndex++;
                        this.handler()
                        return
                    } else if (checkResult.code === 2) {  // "上传中" 状态
                        // 获取已上传分片列表
                        console.log("上传中：", checkResult)
                        currentFile.status = FileStatus.uploading
                        let chunkUploadedList = checkResult.data.chunkUploadedList
                        console.log("chunkUploadedList",chunkUploadedList)
                        currentFile.chunkUploadedList = chunkUploadedList
                        console.log("成功上传的分片信息", chunkUploadedList)
                    } else {   // 未上传
                        console.log('未上传')
                    }
                    // 3. 正在创建分片
                    currentFile.status = FileStatus.chip;

                    //创建分片
                    let fileChunks = self.createFileChunk(currentFile.raw, chunkSize);

                    //重命名文件
                    //let fileName = this.getNewFileName(currentFile)

                    // 获取文件类型
                    //let type = currentFile.name.substring(currentFile.name.lastIndexOf(".") + 1)
                    let type = fileSuffixTypeUtil(currentFile.name)

                    let param = {
                        fileName: currentFile.name,
                        fileSize: currentFile.size,
                        chunkSize: chunkSize,
                        chunkNum: totalChunks,
                        fileMd5: md5,
                        contentType: 'application/octet-stream',
                        fileType: type,
                        //uploadId:localStorage.getItem("file_upload_id"),
                        chunkUploadedList: currentFile.chunkUploadedList//已上传的分片索引+1
                    }
                    // debugger
                    // 4. 获取上传url
                    let uploadIdInfoResult = await self.getFileUploadUrls(param)
                    //debugger

                    let uploadIdInfo = uploadIdInfoResult.data
                    console.log("获取上传url-->", uploadIdInfo)

                    let uploadUrls = uploadIdInfo.urlList

                    self.$set(currentFile, 'chunkList', [])

                    if (uploadUrls !== undefined) {
                        if (fileChunks.length !== uploadUrls.length) {
                            self.$message.error('文件分片上传地址获取错误')
                            return
                        }
                    }
                    fileChunks.map((chunkItem, index) => {
                        if(currentFile.chunkUploadedList.indexOf(index+1)!==-1){
                            currentFile.chunkList.push({
                                chunkNumber: index + 1,
                                chunk: chunkItem,
                                uploadUrl: uploadUrls[index],
                                progress: 100,
                                progressStatus:'success',
                                status: '上传成功'
                            })
                        }else{
                            currentFile.chunkList.push({
                                chunkNumber: index + 1,
                                chunk: chunkItem,
                                uploadUrl: uploadUrls[index],
                                progress: 0,
                                status: '—'
                            })
                        }
                    })
                    console.log("所有分片信息：",currentFile.chunkList)
                    let tempFileChunks = []

                    currentFile.chunkList.forEach((item) => {
                        tempFileChunks.push(item)
                    })

                    //更新状态
                    currentFile.status = FileStatus.uploading

                    // 处理分片列表，删除已上传的分片
                    tempFileChunks = self.processUploadChunkList(tempFileChunks)
                    console.log("删除已上传的分片-->", tempFileChunks)
                    // 5. 上传
                    await self.uploadChunkBase(tempFileChunks)


                    console.log('---上传完成---')

                    //判断是否单文件上传或者分片上传
                    if (uploadIdInfo.uploadId === "SingleFileUpload") {
                        console.log("单文件上传");
                        //更新状态
                        currentFile.status = FileStatus.success
                        //文件下标偏移
                        currentFileIndex++;
                        //递归上传下一个文件
                        this.handler()
                        return
                    } else {
                        // 6. 合并文件
                        console.log("合并文件-->",currentFile)
                        const mergeResult =await self.mergeFile({
                            uploadId: uploadIdInfo.uploadId,
                            fileName: currentFile.name,
                            fileMd5: md5,
                            fileType: type,
                            chunkNum:uploadIdInfo.urlList.length,
                            chunkSize:chunkSize,
                            fileSize:currentFile.size
                        })

                        //合并文件状态
                        if (!mergeResult.data) {
                            currentFile.status = FileStatus.error
                            self.$message.error(mergeResult.error)
                        } else {
                            localStorage.removeItem(FILE_UPLOAD_ID_KEY)
                            currentFile.status = FileStatus.success
                            console.log('文件访问地址：' + mergeResult.data)
                            //文件下标偏移
                            currentFileIndex++;
                            //递归上传下一个文件
                            this.handler()
                        }
                    }
                })
            },


            /**
             * 清空列表
             */
            clearFileHandler() {
                console.log(this.uploadFileList)

                this.uploadFileList = []
                this.uploadIdInfo = null
                this.fileIndex = 0
                currentFileIndex = 0
            },

            /**
             * 上传文件列表
             * @param {*} file
             * @param {*} fileList
             */
            handleFileChange(file, fileList) {
                this.initFileProperties(file)
                this.uploadFileList = fileList

            },

            //初始化文件属性
            initFileProperties(file) {
                file.chunkList = []
                file.status = FileStatus.wait
                file.progressStatus = 'warning'
                file.uploadProgress = 0
            },
            /**
             * 移除文件列表
             * @param {*} file
             * @param {*} fileList
             */
            handleRemove(file, fileList) {
                this.uploadFileList = fileList
            },
            /**
             * 检查上传文件格式
             * @param {*} file
             */
            beforeUploadVideo(file) {
                let type = file.name.substring(file.name.lastIndexOf(".") + 1);
                if (
                    [
                        "mp4",
                        "ogg",
                        "flv",
                        "avi",
                        "wmv",
                        "rmvb"
                    ].indexOf(type) == -1
                ) {
                    this.$message.error("请上传正确的视频格式");
                    return false;
                }
            },

            getNewFileName(file, md5) {
                return new Date().getTime() + file.name
                //return md5+"-"+ file.name
            },


            /**
             * 分片读取文件 获取文件的MD5
             * @param file
             * @param callback
             */
            getFileMd5(file, callback) {
                const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
                const fileReader = new FileReader()
                // 计算分片数
                const totalChunks = Math.ceil(file.size / chunkSize)
                console.log('总分片数：' + totalChunks)
                let currentChunk = 0
                const spark = new SparkMD5.ArrayBuffer()
                loadNext()
                fileReader.onload = function (e) {
                    try {
                        spark.append(e.target.result)
                    } catch (error) {
                        console.log('获取Md5错误：' + currentChunk)
                    }
                    if (currentChunk < totalChunks) {
                        currentChunk++
                        loadNext()
                    } else {
                        callback(spark.end(), totalChunks)
                    }
                }
                fileReader.onerror = function () {
                    console.warn('读取Md5失败，文件读取错误')
                }

                function loadNext() {
                    const start = currentChunk * chunkSize
                    const end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize
                    // 注意这里的 fileRaw
                    fileReader.readAsArrayBuffer(blobSlice.call(file, start, end))
                }
            },
            /**
             * 文件分片
             */
            createFileChunk(file, size = chunkSize) {
                const fileChunkList = []
                let count = 0
                while (count < file.size) {
                    fileChunkList.push({
                        file: file.slice(count, count + size),
                    })
                    count += size
                }
                return fileChunkList
            },
            /**
             * 处理即将上传的分片列表，判断是否有已上传的分片，有则从列表中删除
             */
            processUploadChunkList(chunkList) {
                const currentFile = this.uploadFileList[currentFileIndex]
                let chunkUploadedList = currentFile.chunkUploadedList
                if (chunkUploadedList === undefined || chunkUploadedList === null || chunkUploadedList.length === 0) {
                    return chunkList
                }
                //
                for (let i = chunkList.length - 1; i >= 0; i--) {
                    const chunkItem = chunkList[i]
                    for (let j = 0; j < chunkUploadedList.length; j++) {
                        if (chunkItem.chunkNumber === chunkUploadedList[j]) {
                            chunkList.splice(i, 1)
                            break
                        }
                    }
                }
                return chunkList
            },

            /**
             * 上传分片
             * @param chunkList
             * @returns {Promise<unknown>}
             */
            uploadChunkBase(chunkList) {
                const self = this
                let successCount = 0
                let totalChunks = chunkList.length
                return new Promise((resolve, reject) => {
                    const handler = () => {
                        if (chunkList.length) {

                            const chunkItem = chunkList.shift()
                            // 直接上传二进制，不需要构造 FormData，否则上传后文件损坏
                            axios.put(chunkItem.uploadUrl, chunkItem.chunk.file, {
                                // 上传进度处理
                                onUploadProgress: self.checkChunkUploadProgress(chunkItem),
                                headers: {
                                    'Content-Type': 'application/octet-stream'
                                }
                            }).then(response => {
                                if (response.status === 200) {
                                    console.log('分片：' + chunkItem.chunkNumber + ' 上传成功')
                                    successCount++
                                    // 继续上传下一个分片
                                    //  debugger
                                    handler()
                                } else {
                                    console.log('上传失败：' + response.status + '，' + response.statusText)
                                }
                            })
                                .catch(error => {
                                    // 更新状态
                                    console.log('分片：' + chunkItem.chunkNumber + ' 上传失败，' + error)
                                    // 重新添加到队列中
                                    chunkList.push(chunkItem)
                                    handler()
                                })
                        }
                        if (successCount >= totalChunks) {
                            resolve()
                        }
                    }
                    // 并发
                    for (let i = 0; i < this.simultaneousUploads; i++) {
                        handler()
                    }
                })
            },

            /**
             * 获取直接上传的uri链接
             * @param fileParam
             * @returns {Promise<AxiosResponse<any>>}
             */
            getFileUploadUrls(fileParam) {
                return initUpload(fileParam)
            },

            /**
             * 根据MD5查看文件是否上传过
             * @param md5
             * @returns {Promise<unknown>}
             */
            checkFileUploadedByMd5(md5) {
                return new Promise((resolve, reject) => {
                    checkUpload(md5).then(response => {
                        console.log("md5-->:", response);
                        resolve(response)
                    }).catch(error => {
                        reject(error)
                    })
                })
            },
            /**
             * 合并文件
             *   uploadId: self.uploadIdInfo.uploadId,
             fileName: currentFile.name,
             //fileMd5: fileMd5,
             bucketName: 'bucket'
             */
            mergeFile(fileParam) {
                const self = this;
                return new Promise((resolve, reject) => {
                    mergeUpload(fileParam).then(response => {
                        console.log(response);
                        let data = response
                        console.log("@@@", data)
                        if (!data) {
                            data.msg = FileStatus.error
                            resolve(data)
                        } else {
                            data.msg = FileStatus.success
                            resolve(data)
                        }
                    })
                    // .catch(error => {
                    //     self.$message.error('合并文件失败：' + error)
                    //     file.status = FileStatus.error
                    //     reject()
                    // })
                })
            },
            /**
             * 检查分片上传进度
             */
            checkChunkUploadProgress(item) {
                return p => {
                    item.progress = parseInt(String((p.loaded / p.total) * 100))
                    console.log("进度：", this.uploadFileList[currentFileIndex].uploadProgress)
                    this.updateChunkUploadStatus(item)
                }
            },
            updateChunkUploadStatus(item) {
                let status = FileStatus.uploading
                let progressStatus = 'normal'
                if (item.progress >= 100) {
                    status = FileStatus.success
                    progressStatus = 'success'
                }
                let chunkIndex = item.chunkNumber - 1
                let currentChunk = this.uploadFileList[currentFileIndex].chunkList[chunkIndex]
                // 修改状态
                currentChunk.status = status
                currentChunk.progressStatus = progressStatus
                // 更新状态
                this.$set(this.uploadFileList[currentFileIndex].chunkList, chunkIndex, currentChunk)
                // 获取文件上传进度
                this.getCurrentFileProgress()
            },
            getCurrentFileProgress() {
                const currentFile = this.uploadFileList[currentFileIndex]
                if (!currentFile || !currentFile.chunkList) {
                    return
                }
                const chunkList = currentFile.chunkList
                const uploadedSize = chunkList.map((item) => item.chunk.file.size * item.progress).reduce((acc, cur) => acc + cur)
                // 计算方式：已上传大小 / 文件总大小
                let progress = parseInt((uploadedSize / currentFile.size).toFixed(2))
                // debugger
                currentFile.uploadProgress = progress
                this.$set(this.uploadFileList,currentFileIndex, currentFile)
            },
            deleteFile(file) {
                let deleteIndex = this.uploadFileList.findIndex(item => item.name === file.name)
                this.uploadFileList.splice(deleteIndex, 1)
                this.$message.info("移除成功")
            },
            handleFileUploadSuccess(res) {
                console.log(res)
                this.load()
            },
        },
        filters: {
            transformByte(size) {
                if (!size) {
                    return '0B'
                }
                const unitSize = 1024
                if (size < unitSize) {
                    return size + ' B'
                }
                // KB
                if (size < Math.pow(unitSize, 2)) {
                    return (size / unitSize).toFixed(2) + ' K';
                }
                // MB
                if (size < Math.pow(unitSize, 3)) {
                    return (size / Math.pow(unitSize, 2)).toFixed(2) + ' MB'
                }
                // GB
                if (size < Math.pow(unitSize, 4)) {
                    return (size / Math.pow(unitSize, 3)).toFixed(2) + ' GB';
                }
                // TB
                return (size / Math.pow(unitSize, 4)).toFixed(2) + ' TB';
            }
        }
    };
</script>
<style scoped>
    .container {
        width: 750px;
        margin: 0 auto;
    }

    .file-list-wrapper {
        margin-top: 20px;
    }

    h2 {
        text-align: center;
    }

    .file-info-item {

        margin: 0 10px;
    }

    .upload-file-item {
        display: flex;
    }

    .file-progress {
        display: flex;
        align-items: center;
    }

    .file-progress-value {
        width: 150px;
    }

    .file-name {
        width: 250px;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .file-size {
        width: 100px;
        margin-left: 60px;
    }

    .uploader-example {
        width: 880px;
        padding: 15px;
        margin: 40px auto 0;
        font-size: 12px;
        box-shadow: 0 0 10px rgba(0, 0, 0, .4);
    }

    .uploader-example .uploader-btn {
        margin-right: 4px;
    }

    .uploader-example .uploader-list {
        max-height: 440px;
        overflow: auto;
        overflow-x: hidden;
        overflow-y: auto;
    }
</style>
